package com.redisson;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "/index2")
public class IndexController2 {

    @Autowired
    private Redisson redisson;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        String lockKey = "lock:product_101";
        // 保证是原子操作。设置key的同时也要设置超时时间，使用redis提供的命令
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "getLock", 5, TimeUnit.SECONDS);
        if (!flag) {
            return "false";
        }
        // 1、添加上try...finally...防止代码执行过程中，一定会将锁释放掉
        // 2、当然防止服务以外宕机，key无法进行释放的问题，将当前key设置超时时间
        // 3、但是新的问题又出现了，如何确定key的超时时间呢？根据业务的正常执行时间吗？所以这个超时时间无法来进行确定！！！！↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
        // -------------->问题复现：如果再次出现宕机问题，但是在下面的扣减库存之后，那么就意味着扣减库存之后的代码无法来进行复现！但是实际库存已经剪过了
        // --------------->业务逻辑执行时间执行超过了设置的过期时间，那么这种情况下，将会导致key会被删除掉。导致其他的线程再次来进行加锁
        // --------------->当其他线程执行过程中，当前线程恢复，然后将key给删除掉了！！！！但是当前线程没有执行完成！！！
        // 那么存在两个问题：1、不知道超时时间如何设置；2、线程释放了其他线程的key
        // 最根本的问题在哪里？在于当前线程释放了其他线程的锁，导致出现问题！！！那么只需要保证是当前线程加锁并且是当前线程释放了锁就行
        // 只需要给当前上锁的线程加上一个唯一标识！！！保证当前加锁的线程和上锁的线程是同一个即可！------第三个版本
        try {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // jedis.get("stock")
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key,value)
                System.out.println("扣减成功，剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }


    @RequestMapping("/redlock")
    public String redlock() {
        String lockKey = "product_001";
        //这里需要自己实例化不同redis实例的redisson客户端连接，这里只是伪代码用一个redisson客户端简化了
        RLock lock1 = redisson.getLock(lockKey);
        RLock lock2 = redisson.getLock(lockKey);
        RLock lock3 = redisson.getLock(lockKey);

        /**
         * 根据多个 RLock 对象构建 RedissonRedLock （最核心的差别就在这里）
         */
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            /**
             * waitTimeout 尝试获取锁的最大等待时间，超过这个值，则认为获取锁失败
             * leaseTime   锁的持有时间,超过这个时间锁会自动失效（值应设置为大于业务处理的时间，确保在锁有效期内业务能处理完）
             */
            boolean res = redLock.tryLock(10, 30, TimeUnit.SECONDS);
            if (res) {
                //成功获得锁，在这里处理业务
            }
        } catch (Exception e) {
            throw new RuntimeException("lock fail");
        } finally {
            //无论如何, 最后都要解锁
            redLock.unlock();
        }

        return "end";
    }

}